﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows.Forms;
using System.Collections.ObjectModel;
using System.Drawing;

namespace Dycox.Windows.Forms
{
    [DefaultEvent("RetrieveAutoCompleteSource")]
    [DefaultProperty("DropDownColumns")]
    public class AutoCompleteTextBox : TextBox
    {

        AutoCompleteDropDown _dropDown;
        Timer _closeDropDownTimer;
        GetAutoCompleteSourceContext _context;
        ColumnInfoCollection _dropDownColumns;

        Container _components;

        public AutoCompleteTextBox()
        {
            _components = new Container();

            _closeDropDownTimer = new Timer(_components);
            _closeDropDownTimer.Interval = 100;
            _closeDropDownTimer.Tick += new EventHandler(_closeDropDownTimer_Tick);

            _context = new GetAutoCompleteSourceContext(this);

        }

        //[Editor("System.ComponentModel.Design.CollectionEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(System.Drawing.Design.UITypeEditor))]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public ColumnInfoCollection DropDownColumns
        {
            get
            {
                if (_dropDownColumns == null)
                    _dropDownColumns = new ColumnInfoCollection();
                return _dropDownColumns;
            }
        }

        private object _value;
        [Browsable(false), DefaultValue(null), Bindable(true)]
        public object Value
        {
            get { return _value; }
            set
            {
                SetValue(value, true);
            }
        }

        private void SetValue(object value, bool updateText)
        {
            if (Convert.IsDBNull(value)) value = null;

            if (!object.Equals(_value, value))
            {
                _value = value;
                OnValueChanged(EventArgs.Empty);


                if (updateText)
                    SetText(GetValueText(value));
            }
        }

        private int _dropDownWidth;
        [DefaultValue(0), Category("Appearance")]
        public int DropDownWidth
        {
            get { return _dropDownWidth; }
            set
            {
                if (value < 0)
                    throw new ArgumentOutOfRangeException();

                _dropDownWidth = value;
            }
        }

        private DropDownWidthMode _dropDownWidthMode;

        [DefaultValue(typeof(DropDownWidthMode), "DropDown"), Category("Appearance")]
        public DropDownWidthMode DropDownWidthMode
        {
            get { return _dropDownWidthMode; }
            set { _dropDownWidthMode = value; }
        }

        private bool _freeText;
        [DefaultValue(false), Category("Behavior"), Description("Allows user to input text freely.")]
        public bool FreeText
        {
            get { return _freeText; }
            set { _freeText = value; }
        }


        private int GetDropDownWidth()
        {
            int w;

            switch (_dropDownWidthMode)
            {
                case DropDownWidthMode.DropDown:
                    w = _dropDownColumns.Sum(o => o.Width) + SystemInformation.VerticalScrollBarWidth + SystemInformation.FixedFrameBorderSize.Width * 2;
                    break;
                case DropDownWidthMode.TextBox:
                    w = this.Width;
                    break;
                case DropDownWidthMode.Custom:
                    w = Math.Max(this.Width, _dropDownWidth);
                    break;
                default:
                    throw new InvalidOperationException();
            }

            return w;
        }


        public event EventHandler ValueChanged;

        protected void OnValueChanged(EventArgs e)
        {
            if (ValueChanged != null)
                ValueChanged(this, e);
        }

        private int _maxDropDownItems = 20;
        [DefaultValue(20)]
        public int MaxDropDownItems
        {
            get { return _maxDropDownItems; }
            set { _maxDropDownItems = value; }
        }

        private int _dropDownHeight = 200;
        [DefaultValue(200)]
        public int DropDownHeight
        {
            get { return _dropDownHeight; }
            set { _dropDownHeight = value; }
        }

        void _closeDropDownTimer_Tick(object sender, EventArgs e)
        {
            _closeDropDownTimer.Stop();
            if (_dropDown != Form.ActiveForm)
                ToggleDropDown(false);
        }

        private void ToggleDropDown(bool visible)
        {
            if (visible)
            {
                if (_dropDown == null)
                {
                    _dropDown = new AutoCompleteDropDown(this);
                    _components.Add(_dropDown);
                }
                if (!_dropDown.Visible)
                    _dropDown.Show();
            }
            else
            {
                if (_dropDown != null)
                    _dropDown.Hide();
            }
        }

        [Browsable(false)]
        public bool DropDownVisible
        {
            get
            {
                return _dropDown != null && _dropDown.Visible;
            }
        }

        protected override void OnLostFocus(EventArgs e)
        {
            base.OnLostFocus(e);

            _closeDropDownTimer.Start();
        }


        bool _preventing;
        protected override void OnTextChanged(EventArgs e)
        {
            if (!_preventing)
            {
                SetValue(null, false);
                _context.Keyword = this.Text;
            }
            _preventing = false;

            base.OnTextChanged(e);
        }

        public override string Text
        {
            get
            {
                return base.Text;
            }
            set
            {
                if (IsHandleCreated)
                {
                    _preventing = true;
                }
                base.Text = value;
            }
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Down || e.KeyCode == Keys.Up)
            {
                if (!DropDownVisible && _dropDown != null && !_context.IsBusy && this.TextLength > 0)
                    ToggleDropDown(true);

                if (DropDownVisible)
                {
                    _dropDown.Select();
                    if (e.KeyCode == Keys.Up)
                        _dropDown.SelectLastItem();
                    else
                        _dropDown.SelectFirstItem();
                }
                e.Handled = true;
            }
            else if (e.KeyCode == Keys.Escape)
            {
                ToggleDropDown(false);
                e.Handled = true;
            }

            if (!e.Handled)
                base.OnKeyDown(e);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _components.Dispose();
            }
            base.Dispose(disposing);
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new AutoCompleteMode AutoCompleteMode
        {
            get { return base.AutoCompleteMode; }
            set { }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new AutoCompleteSource AutoCompleteSource
        {
            get { return base.AutoCompleteSource; }
            set { }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new AutoCompleteStringCollection AutoCompleteCustomSource
        {
            get { return base.AutoCompleteCustomSource; }
            set { }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public override bool Multiline
        {
            get
            {
                return base.Multiline;
            }
            set
            {
                base.Multiline = value;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new string[] Lines
        {
            get { return base.Lines; }
            set { base.Lines = value; }
        }



        public event EventHandler<RetrieveAutoCompleteSourceEventArgs> RetrieveAutoCompleteSource
        {
            add { _context.RetrieveAutoCompleteSource += value; }
            remove { _context.RetrieveAutoCompleteSource -= value; }
        }

        private void SetDataSource(object dataSource)
        {
            if (dataSource != null)
            {
                if (this.Focused)
                {
                    ToggleDropDown(true);
                    _dropDown.SetDataSource(dataSource);
                }
                else
                {
                    if (!TrySetUniqueValue(dataSource, true))
                        this.Value = null;
                }
            }
            else
                ToggleDropDown(false);
        }

        private bool TrySetUniqueValue(object dataSource, bool updateText)
        {
            System.Collections.ICollection coll = dataSource as System.Collections.ICollection;
            if (coll != null && coll.Count == 1)
            {
                var e = coll.GetEnumerator();
                e.MoveNext();
                SetValue(e.Current, updateText);
                return true;
            }
            else
                return false;
        }

        private void CommitSelection(object item, string text)
        {
            this.Value = item;
            ToggleDropDown(false);
        }

        private string GetValueText(object value)
        {
            if (value != null)
            {
                return value.ToString();
            }
            else
                return string.Empty;
        }

        protected override void OnValidated(EventArgs e)
        {
            base.OnValidated(e);
        }

        private void SetText(string text)
        {
            _preventing = true;
            base.Text = text;
            _preventing = false;
        }

        protected override void OnValidating(CancelEventArgs e)
        {
            //if (!_context.IsBusy && _context.LastResult != null && _context.Keyword != Text)
            //{
            //    if (this.TextLength == 0)
            //    {
            //        SetValue(null, false);
            //    }
            //    else
            //    {
            //        bool update = true;
            //        if (_value == null && _context.LastResult != null && TextLength > 0)
            //            update = !TrySetUniqueValue(_context.LastResult);

            //        if (update)
            //        {
            //            if (!_freeText)
            //                SetText(GetValueText(_value));
            //            else if (_context.Keyword != Text)
            //                _value = null;
            //        }
            //    }
            //}

            if (!_context.IsBusy && _context.LastResult != null)
            {
                if (_value == null && TextLength > 0)
                {
                    if (!_freeText)
                    {
                        if (!TrySetUniqueValue(_context.LastResult, true))
                            SetText(string.Empty);
                    }
                    //else
                    //    TrySetUniqueValue(_context.LastResult, false);
                }
            }

            base.OnValidating(e);
        }

        class GetAutoCompleteSourceContext
        {
            public event EventHandler<RetrieveAutoCompleteSourceEventArgs> RetrieveAutoCompleteSource;

            private AutoCompleteTextBox _owner;
            private AsyncOperation _asyncOp;

            public GetAutoCompleteSourceContext(AutoCompleteTextBox owner)
            {
                _owner = owner;
            }

            string _keyword;

            public string Keyword
            {
                get
                {
                    return _keyword;
                }
                set
                {
                    if (_keyword != value)
                    {
                        _keyword = value;
                        GetDataAsync(value, false);
                    }
                }
            }

            private void GetDataAsync(string keyword, bool force)
            {
                if (_asyncOp == null || force)
                {
                    _asyncOp = AsyncOperationManager.CreateOperation(keyword);
                    GetDataAsync(_asyncOp);
                }
            }

            private void GetDataAsync(AsyncOperation ao)
            {
                System.Threading.SendOrPostCallback getData = new System.Threading.SendOrPostCallback(GetData);
                getData.BeginInvoke(ao, null, null);
            }

            private void GetData(object state)
            {
                AsyncOperation ao = (AsyncOperation)state;
                string keyword = (string)ao.UserSuppliedState;
                object dataSource = OnGetData(keyword);

                if (keyword == _keyword)
                {
                    _lastResult = dataSource;
                    ao.PostOperationCompleted(_owner.SetDataSource, dataSource);
                    _asyncOp = null;
                }
                else
                {
                    keyword = _keyword;
                    ao.PostOperationCompleted(
                        (foo) =>
                        {
                            GetDataAsync(keyword, true);
                        }, null);
                }

            }

            private object OnGetData(string keyword)
            {
                if (RetrieveAutoCompleteSource != null)
                {
                    RetrieveAutoCompleteSourceEventArgs e = new RetrieveAutoCompleteSourceEventArgs(keyword, _owner.MaxDropDownItems);
                    RetrieveAutoCompleteSource(_owner, e);
                    return e.DataSource;
                }
                else
                    return null;
            }

            public bool IsBusy
            {
                get { return _asyncOp != null; }
            }

            private object _lastResult;

            public object LastResult
            {
                get { return _lastResult; }
            }

        }

        //class DataBoundColumnHeader : ColumnHeader
        //{
        //    public DataBoundColumnHeader()
        //    {

        //    }

        //    private string _propertyName;
        //    private PropertyDescriptor _property;

        //    public string PropertyName
        //    {
        //        get { return _propertyName; }
        //        set { _propertyName = value; }
        //    }


        //    private string _displayFormat;

        //    public string DisplayFormat
        //    {
        //        get { return _displayFormat; }
        //        set { _displayFormat = value; }
        //    }

        //    public string GetText(object dataItem)
        //    {
        //        if (dataItem == null)
        //            return string.Empty;

        //        if (_propertyName == null)
        //        {
        //            //IListItem iil = dataItem as IListItem;
        //            //if (iil != null) return iil.Text;
        //            //else
        //            return dataItem.ToString();
        //        }

        //        if (_property == null || _property.ComponentType != dataItem.GetType())
        //        {
        //            _property = TypeDescriptor.GetProperties(dataItem.GetType())[_propertyName];
        //        }

        //        if (_property != null)
        //        {
        //            object value = _property.GetValue(dataItem);

        //            if (value != null)
        //            {
        //                if (value is string) return (string)value;

        //                string text;

        //                if (_displayFormat != null)
        //                    text = string.Format(_displayFormat, value);
        //                else
        //                    text = value.ToString();

        //                return text;
        //            }
        //        }

        //        return string.Empty;

        //    }

        //}

        class AutoCompleteDropDown : Form
        {
            DataListView _listView;
            AutoCompleteTextBox _owner;

            public AutoCompleteDropDown(AutoCompleteTextBox owner)
            {
                if (owner == null)
                    throw new ArgumentNullException("owner");

                _owner = owner;
                Form form = owner.FindForm();
                form.LocationChanged += new EventHandler(form_LocationChanged);

                this.SuspendLayout();

                this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
                this.MaximizeBox = this.MinimizeBox = this.ControlBox = false;
                this.ShowInTaskbar = false;
                this.StartPosition = FormStartPosition.Manual;
                this.KeyPreview = true;
                this.MinimumSize = new System.Drawing.Size(10, 10);

                _listView = new DataListView();
                _listView.MultiSelect = false;
                _listView.FullRowSelect = true;
                _listView.Dock = DockStyle.Fill;
                _listView.MouseClick += new MouseEventHandler(_listView_MouseClick);
                _listView.MouseMove += new MouseEventHandler(_listView_MouseMove);
                _listView.HideSelection = false;
                _listView.DrawItem += new DrawListViewItemEventHandler(_listView_DrawItem);
                _listView.DrawSubItem += new DrawListViewSubItemEventHandler(_listView_DrawSubItem);
                _listView.DrawColumnHeader += new DrawListViewColumnHeaderEventHandler(_listView_DrawColumnHeader);
                _listView.OwnerDraw = true;
                CreateColumns();

                this.Controls.Add(_listView);

                this.ResumeLayout();

            }

            public const int WS_EX_NOACTIVATE = 0x8000000;

            protected override CreateParams CreateParams
            {
                get
                {
                    var cp = base.CreateParams;

                    //cp.ExStyle = WS_EX_NOACTIVATE;

                    cp.ClassStyle |= NativeMethods.CS_DROPSHADOW;
                    return cp;
                }
            }

            private void CreateColumns()
            {
                if (_owner.DropDownColumns.Count > 0)
                {
                    foreach (var info in _owner.DropDownColumns)
                    {
                        DataColumnHeader col = new DataColumnHeader();
                        col.DataMember = info.PropertyName;
                        col.DataDisplayFormat = info.DisplayFormat;
                        col.Width = info.Width;
                        col.Text = info.HeaderText;
                        col.TextAlign = info.Alignment;

                        _listView.Columns.Add(col);
                    }

                    _listView.HeaderStyle = ColumnHeaderStyle.Nonclickable;
                }
                else
                {
                    DataColumnHeader col = new DataColumnHeader();
                    _listView.Columns.Add(col);
                    _listView.HeaderStyle = ColumnHeaderStyle.None;
                }
            }

            void _listView_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
            {
                e.DrawDefault = true;
            }

            //protected override void OnLoad(EventArgs e)
            //{
            //    SetWindowLong(this.Handle, GWL_EXSTYLE, GetWindowLong(this.Handle, GWL_EXSTYLE) | WS_EX_NOACTIVATE); 
            //    base.OnLoad(e);
            //}

            void _listView_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
            {
                ColumnHeader col = _listView.Columns[e.ColumnIndex];
                TextFormatFlags flags = TextFormatFlags.VerticalCenter;

                switch (col.TextAlign)
                {
                    case HorizontalAlignment.Left:
                        flags |= TextFormatFlags.Left;
                        break;
                    case HorizontalAlignment.Center:
                        flags |= TextFormatFlags.HorizontalCenter;
                        break;
                    case HorizontalAlignment.Right:
                        flags |= TextFormatFlags.Right;
                        break;
                }

                TextRenderer.DrawText(e.Graphics, e.SubItem.Text, _listView.Font, e.Bounds,
                    e.Item.Selected ? SystemColors.HighlightText : _listView.ForeColor, flags);
            }

            void _listView_DrawItem(object sender, DrawListViewItemEventArgs e)
            {
                if (e.Item.Selected)
                    e.Graphics.FillRectangle(SystemBrushes.Highlight, e.Bounds);
            }

            void _listView_MouseClick(object sender, MouseEventArgs e)
            {
                ListViewItem item = _listView.GetItemAt(e.X, e.Y);
                CommitSelection(item);
            }

            private void CommitSelection(ListViewItem item)
            {
                DataListViewItem dlvi = item as DataListViewItem;
                if (dlvi != null)
                    _owner.CommitSelection(dlvi.DataItem, item.Text);
            }

            void _listView_MouseMove(object sender, MouseEventArgs e)
            {
                var item = _listView.GetItemAt(e.X, e.Y);
                if (item != null)
                {
                    item.Selected = true;
                    _listView.FocusedItem = item;
                }
            }

            protected override void OnKeyDown(KeyEventArgs e)
            {
                if (e.KeyCode == Keys.Escape)
                {
                    _owner.ToggleDropDown(false);
                    e.Handled = true;
                }
                else if (e.KeyCode == Keys.Enter)
                {
                    CommitSelection(_listView.FocusedItem);
                    e.Handled = true;
                }

                if (!e.Handled)
                    base.OnKeyDown(e);
            }

            protected override bool ShowWithoutActivation
            {
                get
                {
                    return _owner != null;
                }
            }

            public new void Show()
            {
                if (!IsDisposed)
                {
                    this.ClientSize = new System.Drawing.Size(_owner.GetDropDownWidth(), _owner.DropDownHeight);
                    if (_listView.HeaderStyle == ColumnHeaderStyle.None)
                        _listView.Columns[0].Width = this.Width - SystemInformation.VerticalScrollBarWidth - 5;

                    SyncLocation();
                    base.Show(_owner);
                }
                else
                    throw new ObjectDisposedException(this.GetType().Name);
            }

            private void SyncLocation()
            {
                Point location = _owner.Parent.PointToScreen(_owner.Location);
                Rectangle scrn = Screen.FromControl(_owner).WorkingArea;


                if (location.Y + _owner.Height + this.Height < scrn.Bottom)
                    location.Offset(0, _owner.Height);
                else
                    location.Offset(0, -this.Height);

                this.Location = location;
            }

            void form_LocationChanged(object sender, EventArgs e)
            {
                if (this.Visible)
                    SyncLocation();
            }

            protected override void Dispose(bool disposing)
            {
                if (disposing)
                {
                    if (_owner != null)
                    {
                        Form form = _owner.FindForm();
                        if (form != null)
                        {
                            form.LocationChanged -= form_LocationChanged;
                            _owner = null;
                        }
                    }
                }
                base.Dispose(disposing);
            }


            internal void SetDataSource(object dataSource)
            {
                System.Collections.IEnumerable coll = dataSource as System.Collections.IEnumerable;

                _listView.Items.Clear();
                if (coll != null)
                {
                    int max = _owner.MaxDropDownItems;
                    //    int  count = 0;
                    //foreach (object obj in coll)
                    //{
                    //    ListViewItem item = CreateListViewItem(obj);
                    //    if (item != null)
                    //        _listView.Items.Add(item);

                    //    if (++count > max)
                    //        break;
                    //}
                    _listView.DataSource = coll.Cast<object>().Take(max);
                }

                if (_listView.Items.Count > 0)
                {
                    foreach (ColumnHeader col in _listView.Columns)
                    {
                        if (col.Width == 0)
                            _listView.AutoResizeColumn(col.Index, ColumnHeaderAutoResizeStyle.ColumnContent);
                    }
                }
            }

            //private ListViewItem CreateListViewItem(object data)
            //{
            //    ListViewItem item = new ListViewItem();

            //    foreach (DataBoundColumnHeader col in _listView.Columns)
            //    {
            //        string text = col.GetText(data);
            //        if (col.Index == 0)
            //            item.Text = text;
            //        else
            //            item.SubItems.Add(text);
            //    }

            //    item.Tag = data;

            //    return item;
            //}

            private void SelectItemAt(int index)
            {
                if (index >= 0 && index < _listView.Items.Count)
                {
                    ListViewItem item = _listView.Items[index];
                    _listView.FocusedItem = (DataListViewItem)item;
                    item.Selected = true;
                    item.EnsureVisible();
                }
            }

            public void SelectFirstItem()
            {
                SelectItemAt(0);
            }

            public void SelectLastItem()
            {
                SelectItemAt(_listView.Items.Count - 1);
            }


        }
    }

    public class RetrieveAutoCompleteSourceEventArgs : EventArgs
    {
        public RetrieveAutoCompleteSourceEventArgs(string keyword, int maxItems)
        {
            _keyword = keyword;
            _maxItems = maxItems;
        }

        private int _maxItems;

        public int MaxItems
        {
            get { return _maxItems; }
        }

        private readonly string _keyword;

        public string Keyword
        {
            get { return _keyword; }
        }

        private object _dataSource;

        public object DataSource
        {
            get { return _dataSource; }
            set { _dataSource = value; }
        }
    }

    [Serializable]
    public class ColumnInfo
    {
        public ColumnInfo()
        {

        }

        public ColumnInfo(string headerText, int width, string propertyName)
        {
            HeaderText = headerText;
            Width = width;
            PropertyName = propertyName;
        }

        private string _headerText;

        public string HeaderText
        {
            get { return _headerText; }
            set { _headerText = value; }
        }

        private string _propertyName;

        public string PropertyName
        {
            get { return _propertyName; }
            set { _propertyName = value; }
        }

        private string _displayFormat;

        public string DisplayFormat
        {
            get { return _displayFormat; }
            set { _displayFormat = value; }
        }

        private int _width = 80;
        [DefaultValue(80)]
        public int Width
        {
            get { return _width; }
            set { _width = value; }
        }

        private HorizontalAlignment _alignment = HorizontalAlignment.Left;
        [DefaultValue(typeof(HorizontalAlignment), "Left")]
        public HorizontalAlignment Alignment
        {
            get { return _alignment; }
            set { _alignment = value; }
        }

        public override string ToString()
        {
            return _headerText ?? _propertyName ?? this.GetType().Name;
        }

    }

    public class ColumnInfoCollection : Collection<ColumnInfo>
    {
        public ColumnInfoCollection()
        {

        }

        public void AddRange(IEnumerable<ColumnInfo> coll)
        {
            foreach (var info in coll)
                this.Add(info);
        }

        public ColumnInfo Add(string text, string propertyName)
        {
            return this.Add(text, propertyName, 80);
        }

        public ColumnInfo Add(string text, string propertyName, int width)
        {
            ColumnInfo info = new ColumnInfo();
            info.PropertyName = propertyName;
            info.Width = width;
            info.HeaderText = text;

            this.Add(info);

            return info;
        }
    }

    public enum DropDownWidthMode
    {
        DropDown,
        TextBox,
        Custom
    }
}
